package com.bskj.kuaifu.service.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.annotation.Resource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSONObject;
import com.bskj.common.bean.model.BoxRailInfo;
import com.bskj.common.bean.model.NoCarnoParkInfo;
import com.bskj.common.bean.model.ParkInfo;
import com.bskj.common.bean.model.ParkLotWechatPayInfo;
import com.bskj.common.bean.model.ParkingLotBoxInfo;
import com.bskj.common.bean.model.ParkingLotCustomer;
import com.bskj.common.bean.model.PayInfo;
import com.bskj.common.bean.model.UserInfo;
import com.bskj.common.bean.model.UserMoneyLog;
import com.bskj.common.bean.vo.WechatPayVo;
import com.bskj.common.dao.BoxRailInfoDao;
import com.bskj.common.dao.NoCarnoParkInfoDao;
import com.bskj.common.dao.ParkingLotBoxInfoDao;
import com.bskj.common.dao.PayInfoDao;
import com.bskj.common.dao.core.BaseDao;
import com.bskj.common.dao.core.FindRequestEntity;
import com.bskj.common.dao.core.OrderEnum;
import com.bskj.common.dao.core.WhereEnum;
import com.bskj.common.enums.ParkChargeTypeEnum;
import com.bskj.common.enums.RailTypeEnum;
import com.bskj.common.exception.DataErrorException;
import com.bskj.common.exception.DataNotFindException;
import com.bskj.common.service.impl.BaseServiceImpl;
import com.bskj.common.utils.Global;
import com.bskj.common.utils.H5PayUtils;
import com.bskj.common.utils.LogUtils;
import com.bskj.common.utils.StringUtils;
import com.bskj.kuaifu.bean.vo.UpRailMessage;
import com.bskj.kuaifu.service.IParkInfoService;
import com.bskj.kuaifu.service.IParkingLotCustomerService;
import com.bskj.kuaifu.service.IPayInfoService;
import com.bskj.kuaifu.service.ITopupActivityService;
import com.bskj.kuaifu.service.IUserInfoService;
import com.bskj.kuaifu.service.IUserMoneyLogService;
import com.github.wxpay.sdk.WXPay;
import com.github.wxpay.sdk.WXPayConstants;
import com.github.wxpay.sdk.WXPayUtil;

/**
 * @author lishi
 * @date 2017-11-29 下午4:03:25
 */
@Service
public class PayInfoServiceImpl extends BaseServiceImpl<PayInfo> implements IPayInfoService {
    private static final Logger LOG = LoggerFactory.getLogger(PayInfoServiceImpl.class);

    @Autowired
    private PayInfoDao payInfoDao;

    @Autowired
    private IUserInfoService userInfoService;

    @Autowired
    private IUserMoneyLogService userMoneyLogService;

    @Autowired
    private IParkInfoService parkInfoService;

    @Autowired
    private ITopupActivityService topupActivityService;

    @Autowired
    private WXPay wxPay;
    
    @Autowired
    private NoCarnoParkInfoDao noCarnoParkInfoDao;
    
    @Autowired
    private BoxRailInfoDao boxRailInfoDao;
    
    @Autowired
    private ParkingLotBoxInfoDao parkingLotBoxInfoDao;
    
    @Autowired
    private IParkingLotCustomerService parkingLotCustomerService;
    
    @Resource(name = "myAmqpTemplate")
	private AmqpTemplate amqpTemplate;	

    public BaseDao<PayInfo> getBaseDao() {
        return payInfoDao;
    }

    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> finishedBalancePay(Map<String, String> notifyMap,boolean joinActivity) throws Exception {
        /*
         * TODO
		 * 如果发生异常回滚，这条回调记录就会丢失，造成用户已经支付，但是充值失败的结果。
		 * 后期考虑使用MQ来优化
		 */

        String errorMsg = "";
        Map<String, Object> resultMap = new HashMap<String, Object>();
        String appid = notifyMap.get("appid"); // 支付的微信公众号ID
        String openId = notifyMap.get("openid"); // 用户的openId
        String orderNo = notifyMap.get("out_trade_no"); // 自己的订单号
        int totalFee = Integer.valueOf(notifyMap.get("total_fee")); // 订单支付金额

        // 根据appId + openId 找到用户
        FindRequestEntity findUserReqEntity = new FindRequestEntity();
        findUserReqEntity.addWhere("appid", WhereEnum.EQ, appid);
        findUserReqEntity.addWhere("openId", WhereEnum.EQ, openId);
        List<UserInfo> userInfoList = userInfoService.find(findUserReqEntity);
        if (userInfoList != null && userInfoList.size() > 0) {
            UserInfo userInfo = userInfoList.get(0);
            /*
             * 根据订单同步锁，防止微信重复回调造成金额处理错误（具体可行性 有待考量，获取使用MQ更优？）
			 * 锁住这片代码是因为其它线程执行完成以后，订单状态会发生变化，不会造成重复金额的问题。
			 * 使用orderNo 因为是全局唯一的，对性能不会造成太多影响（未验证）
			 */
            synchronized (orderNo) {
                // 根据订单号查找该支付订单
                FindRequestEntity findOrderReqEntity = new FindRequestEntity();
                findOrderReqEntity.addWhere("order_no", WhereEnum.EQ, orderNo);
                List<PayInfo> payInfoList = find(findOrderReqEntity);
                if (payInfoList != null && payInfoList.size() > 0) {
                    PayInfo payInfo = payInfoList.get(0);
                    if (vaildNotifyParam(notifyMap, userInfo, payInfo)) {
                        /*
                         * 1、更新订单状态
						 * 2、更新用户金额
						 * 3、记录用户资金日志
						 * 
						 * 4、处理活动赠券  (如果需要)
						 */

                        //1、更新订单状态   先判断该订单是否已经完成，如果完成，直接返回
                        if (payInfo.getStatus() != 0) {
                            return resultMap;
                        }
                        payInfo.setTransactionId(notifyMap.get("transaction_id"));
                        payInfo.setTradeType(notifyMap.get("trade_type"));
                        payInfo.setBankType(notifyMap.get("bank_type"));
                        payInfo.setTotalFee(totalFee);
                        payInfo.setRemainMoney(totalFee);
                        String resultCode = notifyMap.get("return_code");
                        if (WXPayConstants.SUCCESS.equals(resultCode)) {
                            payInfo.setStatus(1);
                        }
                        payInfo.setUpdateTime(new Date());
                        update(payInfo);

                        //2、更新用户金额
                        int beforeMoney = userInfo.getMoney();
                        int changeMoney = totalFee / 10; //没有分，可以直接转换为角
                        int afterMoney = beforeMoney + changeMoney;
                        userInfo.setMoney(afterMoney);
                        userInfoService.update(userInfo);

                        //3、记录用户资金日志
                        UserMoneyLog userMoneyLog = new UserMoneyLog();
                        userMoneyLog.setUserId(userInfo.getId());
                        userMoneyLog.setChangeType(1);
                        userMoneyLog.setOrderNo(orderNo);
                        userMoneyLog.setBeforeMoney(beforeMoney);
                        userMoneyLog.setChangeMoney(changeMoney);
                        userMoneyLog.setAfterMoney(afterMoney);
                        userMoneyLog.setBeforeCoupon(userInfo.getCoupon());
                        userMoneyLog.setChangeCoupon(0);
                        userMoneyLog.setAfterCoupon(userInfo.getCoupon());
                        userMoneyLog.setCreateTime(new Date());
                        userMoneyLog.setUpdateTime(new Date());
                        userMoneyLogService.insert(userMoneyLog);

                        if(joinActivity) {
                        	//4、处理活动赠券
                        	topupActivityService.joinPayActivity(userInfo, changeMoney);
                        }

                        resultMap.put("result", WXPayConstants.SUCCESS);
                        resultMap.put("userInfo", userInfo);
                    } else {
                        //TODO 如果数据异常 如何处理
                    }
                } else {
                    errorMsg = String.format("orderNo【%s】 don't find payInfo", orderNo);
                    LOG.error(errorMsg);
                    throw new DataNotFindException(errorMsg);
                }
            }
        } else {
            errorMsg = String.format("appid【%s】 openId【%s】 don't find UserInfo", appid, openId);
            LOG.error(errorMsg);
            throw new DataNotFindException(errorMsg);
        }
        return resultMap;
    }

    @Transactional(rollbackFor = Exception.class)
    public Map<String, Object> finishedArrearsPay(Map<String, String> notifyMap) throws Exception {
        Map<String, Object> resultMap = finishedBalancePay(notifyMap,false);
        if (WXPayConstants.SUCCESS.equals(resultMap.get("result"))) {
            // 如果是欠费支付回调，还需要付清所欠费的单
            UserInfo userInfo = (UserInfo) resultMap.get("userInfo");
            if (userInfo != null) {
                FindRequestEntity findParkInfoEntity = new FindRequestEntity();
                findParkInfoEntity.addWhere("user_id", WhereEnum.EQ, userInfo.getId());
                findParkInfoEntity.addWhere("charge_type", WhereEnum.EQ, ParkChargeTypeEnum.NOT_PAY.getKey());

                findParkInfoEntity.addOrder("create_time", OrderEnum.DESC);
                List<ParkInfo> parkInfoList = parkInfoService.find(findParkInfoEntity);

                if (parkInfoList != null && parkInfoList.size() > 0) {
//                    for (ParkInfo parkInfo : parkInfoList) {
//                        int totalChangeMoney = parkInfo.getCharge();
//
//                        int beforeCoupon = userInfo.getCoupon();
//                        int beforeMoney = userInfo.getMoney();
//
//                        int canUserCoupon = couponRuleCache.getCanUserCoupon(totalChangeMoney);
//                        int changeCoupon = beforeCoupon > canUserCoupon ? canUserCoupon : beforeCoupon;
//                        int changeMoney = totalChangeMoney - changeCoupon;
//
//                        int afterCoupon = beforeCoupon - changeCoupon;
//                        int afterMoney = beforeMoney - changeMoney;
//                        parkInfoService.pay(parkInfo, userInfo, beforeMoney, changeMoney, afterMoney, beforeCoupon,
//                                changeCoupon, afterCoupon, ParkChargeTypeEnum.PLATFROM_PAY);
//                    }

                }
            }

        }
        return resultMap;
    }
    
	public Map<String, String> noCarnoUnifiedOrder(String appId, String openId,Integer parkId, String ip,
			String notifyUrl) throws Exception {
		Map<String, String> resultMap  = new HashMap<>();
		NoCarnoParkInfo parkInfo = noCarnoParkInfoDao.get(parkId);
		if(parkInfo != null && parkInfo.getCharge() > 0) {
			ParkLotWechatPayInfo wechatPayInfo = parkingLotCustomerService
					.getWechatPayInfo(parkInfo.getParkingLotCustomerId());
			ParkingLotCustomer parkingLot = parkingLotCustomerService.get(parkInfo.getParkingLotCustomerId());
			if(wechatPayInfo != null && parkingLot != null) {
				WechatPayVo vo = new WechatPayVo(wechatPayInfo.getAppid(), wechatPayInfo.getMerchantCode(),
						wechatPayInfo.getStoreCode(), wechatPayInfo.getOperator(), wechatPayInfo.getSignType(),
						wechatPayInfo.getSignKey(), parkingLot.getName());
				vo.setAmount((parkInfo.getCharge() * 10) + "");
//				vo.setAmount("1");
				String outTradeNo = StringUtils.getRandomWithTimestamp(32);
				vo.setClientIp(ip);
				vo.setOutTradeNo(outTradeNo);
				vo.setNotifyUrl(notifyUrl);
				if(appId.equals(wechatPayInfo.getSubAppId())) {
					vo.setSubOpenId(openId);
					JSONObject resp = H5PayUtils.genPayOrder(vo);
					LogUtils.debug("resp : " + resp);
					if(resp != null) {
						JSONObject data = resp.getJSONObject("data");
						LogUtils.debug("data : " + data);
						if(data != null) {
							String credentialStr = data.getString("credential");
							LogUtils.debug("credentialStr : " + credentialStr);
							JSONObject credential = JSONObject.parseObject(credentialStr);
							String thirdTradeNo = data.getString("third_trade_no");
							LogUtils.debug("credential : " + credential);
							LogUtils.debug("thirdTradeNo : " + thirdTradeNo);
							if(credential != null) {
								String respAppId = credential.getString("appId");
								String respTimeStamp = credential.getString("timeStamp");
								String respNonceStr = credential.getString("nonceStr");
								String respPackageValue = credential.getString("packageValue");
								String respSignType = credential.getString("signType");
								String respPaySign = credential.getString("paySign");
								if(!StringUtils.isBank(respAppId)) {
									Map<String, String> wxPayParams = new HashMap<String, String>();
									wxPayParams.put("appId", respAppId);
									wxPayParams.put("timeStamp", respTimeStamp);
									wxPayParams.put("nonceStr", respNonceStr);
									wxPayParams.put("package", respPackageValue);
									wxPayParams.put("signType", respSignType);
									wxPayParams.put("paySign", respPaySign);
									LogUtils.debug("wxPayParams:" + JSONObject.toJSONString(wxPayParams));
									resultMap.put("wxPayParams", JSONObject.toJSONString(wxPayParams));
									resultMap.put("orderNo", outTradeNo);
									
									//订单编号关联到parkInfo
									parkInfo.setOrderNo(resultMap.get("orderNo"));
									noCarnoParkInfoDao.update(parkInfo);
									
									//记录支付信息
									PayInfo payInfo = new PayInfo();
									payInfo.setOrderNo(outTradeNo);
									payInfo.setTransactionId(thirdTradeNo);
									payInfo.setTradeType(vo.getChannel());
									payInfo.setNeedFee(Integer.valueOf(vo.getAmount()));
									payInfo.setTotalFee(Integer.valueOf(vo.getAmount()));
									payInfo.setCreateTime(new Date());
									payInfo.setUpdateTime(new Date());
									insert(payInfo);
									return resultMap;
								}
							}
						}
					}
					resultMap.put("error", "订单生成失败");
					LogUtils.error("genOrder resp : " + resp);
					return resultMap;
				} else {
					resultMap.put("error", "订单生成失败");
					LogUtils.error("parkId : "  + parkId + " 因为配置的appid不匹配，无法下单!");
					return resultMap;
				}
			} else {
				resultMap.put("error", "订单生成失败");
				LogUtils.error("parkId : "  + parkId + " 因为配置信息不完善，无法下单!");
				return resultMap;
			}
		} else {
			resultMap.put("error", "订单生成失败");
		}
		return resultMap;
	}

	@Override
    public Map<String, String> unifiedOrder(String appId, String openId, int userId, int totalFee, String ip, String notifyUrl) throws Exception {
        Map<String, String> responeMap = new HashMap<String, String>();

        Map<String, String> params = new HashMap<String, String>();

        params.put("device_info", "WEB");
        params.put("body", "慧泊客-慧付单支付");
        params.put("sign_type", WXPayConstants.SignType.MD5.toString());
        //商户系统内部订单号，要求32个字符内，只能是数字、大小写字母_-|*@ ，且在同一个商户号下唯一。详见商户订单号
        String outTradeNo = StringUtils.getRandomWithTimestamp(32);
        params.put("out_trade_no", outTradeNo);//
        //订单总金额，单位为分
        params.put("total_fee", totalFee + "");
        params.put("spbill_create_ip", ip);
        //params.put("spbill_create_ip", HttpRequestUtil.getIp(getRequest()));
        params.put("notify_url", notifyUrl);
        String tradeType = "JSAPI";
        params.put("trade_type", tradeType);
        //trade_type=JSAPI时（即公众号支付），此参数必传，此参数为微信用户在商户对应appid下的唯一标识。
        params.put("openid", openId);
        LogUtils.debug("unifiedOrder.params:" + JSONObject.toJSONString(params));
        Map<String, String> resultMap = wxPay.unifiedOrder(params);
        LogUtils.debug("unifiedOrder.resultMap:" + JSONObject.toJSONString(resultMap));
        String RETURN_CODE = "return_code";
        String return_code = resultMap.get(RETURN_CODE);
        //return_code :SUCCESS/FAIL 此字段是通信标识，非交易标识，交易是否成功需要查看result_code来判断
        if (return_code.equals(WXPayConstants.FAIL)) {
            responeMap.put("error", "连接服务器失败,请重试");
            return responeMap;
        } else if (return_code.equals(WXPayConstants.SUCCESS)) {
            String result_code = resultMap.get("result_code");
            if (result_code.equals(WXPayConstants.FAIL)) {
                responeMap.put("error", "下单失败，请重试");
                return responeMap;
            } else {
                String prepay_id = resultMap.get("prepay_id");

                PayInfo payInfo = new PayInfo();
                payInfo.setPayType(1);//支付方式（1-微信 2-支付宝）
                payInfo.setUserId(userId);
                payInfo.setOrderNo(outTradeNo);
                payInfo.setStatus(0);
                payInfo.setNeedFee(totalFee);
                payInfo.setTotalFee(totalFee);
                LogUtils.debug(JSONObject.toJSONString(payInfo));
                payInfoDao.insert(payInfo);

                Map<String, String> wxPayParams = new HashMap<String, String>();
                wxPayParams.put("appId", appId);
                wxPayParams.put("timeStamp", System.currentTimeMillis() / 1000 + "");
                wxPayParams.put("nonceStr", WXPayUtil.generateNonceStr());
                wxPayParams.put("package", "prepay_id=" + prepay_id);
                wxPayParams.put("signType", "MD5");
                wxPayParams.put("paySign", WXPayUtil.generateSignature(wxPayParams, Global.getConfig("WX_PAY_APIKEY")));
                LogUtils.debug("wxPayParams:" + JSONObject.toJSONString(wxPayParams));
                responeMap.put("wxPayParams", JSONObject.toJSONString(wxPayParams));
                responeMap.put("orderNo", outTradeNo);
            }
        }

        return responeMap;
    }
	
	public Map<String, String> noCarnoPayNotify(JSONObject notifyMap) throws Exception {
		Map<String, String> resultMap = new HashMap<>();
		String return_code = notifyMap.getString("state");
		if (!StringUtils.isBank(return_code) && return_code.equals("00")) {
			/*
			 * 1、更新t_pay_info，完成订单
			 * 2、更新t_no_carno_park_info 完成无牌车停车记录
			 * 3、通知闸口开闸（长连接）
			 */
			String orderNo = notifyMap.getString("out_trade_no"); // 自己的订单号
			synchronized (orderNo) {
				//step 1 : 更新t_pay_info，完成订单
				FindRequestEntity findOrderReqEntity = new FindRequestEntity();
				findOrderReqEntity.addWhere("order_no", WhereEnum.EQ, orderNo);
				List<PayInfo> payInfoList = find(findOrderReqEntity);
				if (payInfoList != null && payInfoList.size() > 0) {
					PayInfo payInfo = payInfoList.get(0);
					//1、更新订单状态   先判断该订单是否已经完成，如果完成，直接返回
					if (payInfo.getStatus() != 0) {
						return resultMap;
					}
					payInfo.setTransactionId(notifyMap.getString("third_trade_no"));
					payInfo.setTotalFee(notifyMap.getIntValue("amount"));
					payInfo.setRemainMoney(notifyMap.getIntValue("amount"));
					if ("00".equals(return_code)) {
						payInfo.setStatus(1);
					}
					payInfo.setUpdateTime(new Date());
					update(payInfo);
				}
				
				// step2 : 更新t_no_carno_park_info 完成无牌车停车记录
				NoCarnoParkInfo noCarnoParkInfo = null;
				FindRequestEntity findNoCarnoParkInfoParam = new FindRequestEntity();
				findNoCarnoParkInfoParam.addWhere("order_no", WhereEnum.EQ, orderNo);
				List<NoCarnoParkInfo> noCarnoParkInfoList = noCarnoParkInfoDao.find(findNoCarnoParkInfoParam);
				if(noCarnoParkInfoList != null && noCarnoParkInfoList.size() > 0) {
					noCarnoParkInfo = noCarnoParkInfoList.get(0);
					noCarnoParkInfo.setChargeType(ParkChargeTypeEnum.PLATFROM_PAY.getKey());
					noCarnoParkInfo.setUpdateTime(new Date());
					noCarnoParkInfoDao.update(noCarnoParkInfo);
				}
				
				//step 3 ： 通知出闸口开闸 （开闸信息入MQ）
				if(noCarnoParkInfo != null) {
					BoxRailInfo railInfo = boxRailInfoDao.get(noCarnoParkInfo.getOutRailId());
					if(railInfo != null) {
						ParkingLotBoxInfo boxInfo = parkingLotBoxInfoDao.get(railInfo.getBoxId());
						if(boxInfo != null) {
							UpRailMessage upRailMsg = new UpRailMessage();
							upRailMsg.setBoxNo(boxInfo.getBoxNo());
							upRailMsg.setParkLotId(boxInfo.getParkLotCustomerId());
							upRailMsg.setType(RailTypeEnum.OUT_RAIL.getKey());
							upRailMsg.setRailNo(railInfo.getRailNo());
							amqpTemplate.convertAndSend("upRailMsgKey", upRailMsg);
						}
					}
				}
			}
		}
		return resultMap;
	}

	private boolean vaildNotifyParam(Map<String, String> notifyMap, UserInfo userInfo, PayInfo payInfo)
            throws Exception {
        String errorMsg = "";
        if (!userInfo.getId().equals(payInfo.getUserId())) {
            errorMsg = String.format("payInfo【%s】 userId not match userInfo", payInfo.getId());
            LOG.error(errorMsg);
            throw new DataErrorException(errorMsg);
        }

        if (payInfo.getNeedFee() != Integer.valueOf(notifyMap.get("total_fee"))) {
            errorMsg = String.format("payInfo【%s】 fee is not match!", payInfo.getId());
            LOG.error(errorMsg);

            //TODO 金额对不上该如何处理？
        }
        return true;
    }
}
